using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using ESRI.ArcGIS.ADF.BaseClasses;
using ESRI.ArcGIS.ADF.CATIDs;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Output;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Geometry;

namespace ActivityInfo.ArcGIS.Tiles
{

    class Exporter
    {
        private readonly IActiveView activeView;
        private readonly TileCoordinateSystem tileCS;

        public Exporter(IMap map)
        {
            this.activeView = (IActiveView)map;
            this.tileCS = new TileCoordinateSystem(map);
        }

        public int CountTiles(int minZoom, int maxZoom, IEnvelope geoBounds)
        {
            int count = 0;
            for (int z = minZoom; z <= maxZoom; ++z)
            {
                IEnvelope tileBounds = tileCS.TileBoundsForGeographicBounds(
                    geoBounds, z);
                count += (int)(tileBounds.Width * tileBounds.Height);
            }
            return count;
        }

        public void ExportTiles(String name, String copyright, int minZoom, int maxZoom, IEnvelope geoBounds, string outputFolder, IProgressTracker tracker )
        {
            // validate arguments
            if (activeView == null)
                throw new ArgumentException("ActiveView cannot be null.");
            if (minZoom > maxZoom)
                throw new ArgumentException("the minimum zoom cannot be greater than the maximum zoom.");
            if (minZoom < 0)
                throw new ArgumentException("the zoomLevel must be greater or equal to zero");
            if (geoBounds == null)
                throw new ArgumentException("the bounds cannot be null.");
            if (geoBounds.XMin > geoBounds.XMax)
                throw new ArgumentException("XMin cannot be greater than XMax");
            if (geoBounds.YMin > geoBounds.YMax)
                throw new ArgumentException("YMax cannot be greater than YMax");

            // count the total number of tiles for the progress tracking
            tracker.start(CountTiles(minZoom, maxZoom, geoBounds));

            // calculate the bounds of the tile 
            for (int zoom = minZoom; zoom <= maxZoom; ++zoom)
            {
                IEnvelope tileBounds = tileCS.TileBoundsForGeographicBounds(geoBounds, zoom);

                //log(String.Format("TileSize = {0}", tileCS.TileWidthForZoomLevel(zoom)));
                //log(String.Format("Calculated tile bounds: ({0}, {1}, {2}, {3})",
                //    tileBounds.XMin, tileBounds.YMin, tileBounds.XMax, tileBounds.YMax));

                for (double tx = tileBounds.XMin; tx != tileBounds.XMax; ++tx)
                {
                    for (double ty = tileBounds.YMin; ty != tileBounds.YMax; ++ty)
                    {
                        ExportTile(outputFolder, zoom, tx, ty);
                        tracker.tileCompleted();

                        if (tracker.isCancelled())
                            return;
                    }
                }
            }

            // write out the js and sample html file
            Dictionary<String, String> dict = MakeDictionary(name, copyright, minZoom, maxZoom, geoBounds);
            WriteTemplate("Tiles.js", dict, outputFolder + "\\" + name + ".js");
            WriteTemplate("Tiles.html", dict, outputFolder + "\\" + name + ".html");
        }

        private static void log(String message) {
            MessageBox.Show(message);
        }

        private void ExportTile(String outputFolder, int zoom, double tx, double ty)
        {

            IExport export = new ExportPNGClass();
            export.ExportFileName = outputFolder + "\\z" + zoom + "x"  + tx + "y" + ty + ".png";
            export.Resolution = 96;

            //log("Exporting tile to " + export.ExportFileName);

            // Create a new envelope object and populate it with the values from exportRECT.
            // We need to do this because the exporter object requires an envelope object
            // instead of a tagRECT structure.
            IEnvelope pPixelBoundsEnv = new EnvelopeClass();
            pPixelBoundsEnv.PutCoords(0, 0, 256, 256);

            // Assign the envelope object to the exporter object's PixelBounds property.  The exporter object
            // will use these dimensions when allocating memory for the export file.
            export.PixelBounds = pPixelBoundsEnv;

            // Initialize the exporter object and store it's device context in the hDC variable.  At this method
            // call, the exporter object will create an empty file and allocate memory based on resolution,
            // bit depth, and pixel bounds.
            int hDC = export.StartExporting();

            tagRECT exportRect = new tagRECT();
			exportRect.left = 0;
			exportRect.right = 256;
			exportRect.top = 0;
			exportRect.bottom = 256;
			
            IEnvelope projectedBounds = tileCS.ProjectedBoundsForTile(zoom, (int)tx, (int)ty);

            // Redraw the active view, rendering it to the exporter object device context instead of the app display.
            // We pass the following values:
            // * hDC is the device context of the exporter object.
            // * pExport.Resolution is 96, the default resolution value for all image export formats.  Default screen
            //   resolution for MS Windows is usually 96dpi.
            // * exportRECT is the tagRECT structure that describes the dimensions of the view that will be rendered.
            //   The values in exportRECT should match those held in the exporter object's PixelBounds property.
            activeView.Output(hDC, 0, ref exportRect, projectedBounds, null);

            // finish
            export.FinishExporting();
            export.Cleanup();
        }

        private Dictionary<String, String> MakeDictionary(string name, string copyright, int minZoom, int maxZoom, IEnvelope geoBounds)
        {
            Dictionary<string, string> dict = new Dictionary<string, string>();
            dict.Add("NAME", name);
            dict.Add("COPYRIGHT", copyright);
            dict.Add("MIN_ZOOM", minZoom.ToString());
            dict.Add("MAX_ZOOM", maxZoom.ToString());
            dict.Add("MIN_LNG", geoBounds.XMin.ToString());
            dict.Add("MIN_LAT", geoBounds.YMin.ToString());
            dict.Add("MAX_LNG", geoBounds.XMax.ToString());
            dict.Add("MAX_LAT", geoBounds.YMax.ToString());
            return dict;
        }

        private void WriteTemplate(String resourceName, Dictionary<String, String> dict, String outputFile)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            StreamReader reader = new StreamReader(assembly.GetManifestResourceStream("ActivityInfo.ArcGIS.Tiles." + resourceName));
            String text = reader.ReadToEnd();

            foreach (String key in dict.Keys)
            {
                text = text.Replace("${" + key + "}", dict[key]);
            }

            StreamWriter writer = new StreamWriter(outputFile);
            writer.Write(text);
            writer.Close();
        }
    }
}
